﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using Business.Logic;
using Newtonsoft.Json;
using System.Xml.Serialization;

namespace Business.Entities
{
    /// <summary>
    /// Represents a Google/ Task
    /// </summary>
    [Serializable]
    public class Task : NilzBusinessBase<Task>
    {
        public const int LengthOfGuid = 36;
        /// <summary>
        /// yyyyMMdd
        /// </summary>
        private const string GoogleStringDateFormat = "yyyyMMdd";

        #region Json Properties
        /// <summary>
        /// The task text
        /// </summary>
        public string name { get; set; }
        
        /// <summary>
        /// Detailed notes
        /// </summary>
        public string notes { get; set; }

        /// <summary>
        /// List IDs of the children tasks of this task
        /// </summary>
        public string[] child_id;

        /// <summary>
        /// List IDs this one's a part of (dont know if it actually can be member of more than one, but it's defined
        /// in goolge as an array
        /// </summary>
        public string[] list_id;

        /// <summary>
        /// Deleted?
        /// </summary>
        public bool deleted;

        /// <summary>
        /// Last modified unbknown format
        /// </summary>
        public long last_modified;

        /// <summary>
        /// Checked, done, complete?
        /// </summary>
        public bool completed;

        /// <summary>
        /// Date due in yyyyMMdd format
        /// </summary>
        public string task_date;

        /// <summary>
        /// The ID in the Google DB
        /// </summary>
        public string id;
        #endregion

        #region Extra properties
        [JsonIgnore]
        [XmlIgnore]
        public string ListId
        {
            get
            {
                if (list_id == null) return null;
                return list_id[0]; 
            }
            set
            {
                list_id = new string[1];
                list_id[0] = value;
            }

        }

        /// <summary>
        /// True if this task has a Google ID (i.e. not temporary guid)
        /// </summary>
        [JsonIgnore]
        [XmlIgnore]
        public bool HasGoogleId
        {
            get { return id != null && id.Length != LengthOfGuid && id.Length > 0; } 
        }

        /// <summary>
        /// Returns google id. Null if temp id.
        /// </summary>
        [JsonIgnore]
        [XmlIgnore]
        public string GoogleId
        {
            get
            {
                if (!HasGoogleId) return null;
                return id;
            }
        }
            /// <summary>
        /// Children tasks in Google 
        /// </summary>
        [JsonIgnore]
        [XmlIgnore]
        public List<Task> Children = new List<Task>();
        
        /// <summary>
        /// The unique EntryId in outlook
        /// </summary>
        [JsonIgnore]
        [XmlIgnore]
        public string OutlookId;

        [XmlIgnore]
        [JsonIgnore]
        public TaskList ParentList;

        /// <summary>
        /// Timestamp for last updated
        /// </summary>
        [JsonIgnore]
        [XmlIgnore]
        public DateTime CreatedAt;

        /// <summary>
        /// Timestamp for last modified. Mirrors last_modified
        /// </summary>
        [JsonIgnore]
        [XmlIgnore]
        public DateTime LastModified
        {
            get
            {
                // Parse _lastmodified
                return StringUtil.GooglTimeToDateTime(last_modified);
            }
            set
            {
                last_modified = StringUtil.DateTimeToGoogleTime(value);
            }
        }

        /// <summary>
        /// Index/Ordinal
        /// </summary>
        [JsonIgnore]
        [XmlIgnore]
        public int Ordinal;

        /// <summary>
        /// 0 = not indented
        /// </summary>
        [JsonIgnore]
        [XmlIgnore]
        public int IndentLevel;

        [JsonIgnore]
        [XmlIgnore]
        public Task ParentTask;

        /// <summary>
        /// Task visible directly above this one
        /// </summary>
        //[JsonIgnore]
        //public Task PriorTask;

        /// <summary>
        /// Id of parent task (if indented) or list
        /// </summary>
        [JsonIgnore]
        [XmlIgnore]
        public string ParentElementId
        {
            get 
            { 
                if (ParentTask != null) return ParentTask.id;
                return ListId;
            }
        }

            /// <summary>
        /// Deadline for task. Mirrors task_date. 
        /// </summary>
        [JsonIgnore]
        [XmlIgnore]
        public DateTime TaskDate
        {
            get
            {
                if (String.IsNullOrEmpty(task_date)) return DateTime.MinValue;
                try
                {
                    var date = DateTime.ParseExact(task_date, GoogleStringDateFormat, new DateTimeFormatInfo());
                    return date;
                }
                catch (Exception)
                {
                    return DateTime.MinValue;
                }
            }
            set
            {
                if (value.Year > 4000) value = DateTime.MinValue;
                if (value == DateTime.MinValue) task_date = "";
                else task_date = value.ToString(GoogleStringDateFormat);
            }
        }

        [JsonIgnore]
        [XmlIgnore]
        public bool HasTaskDate
        {
            get { return TaskDate != DateTime.MinValue; }
            set
            {
                if (value)
                {
                    if (TaskDate == DateTime.MinValue) TaskDate = DateTime.Today;
                }
                else
                {
                    TaskDate = DateTime.MinValue;
                }
            }
        }

        [JsonIgnore]
        [XmlIgnore]
        public string DateStringShort
        {
            get
            {
                if (!HasTaskDate) return "N/A";
                return TaskDate.ToShortDateString(); 
            }
        }


        [JsonIgnore]
        [XmlIgnore]
        public object OutlookTask;

        #endregion

        #region Constructors
        public Task()
        {
            id = Guid.NewGuid().ToString(); // Temp ID before stored to google
        }

        #endregion

        #region Business methods

        public override string ToString()
        {
            string cp = completed ? "[X]" : "[ ]";
            return Ordinal + ":"+ cp + " " + name;
        }

        /// <summary>
        /// Copies properties form given task
        /// </summary>
        /// <param name="source"></param>
        public void CopyProperties(Task source, bool includeOrdinal)
        {
            name = source.name;
            notes = source.notes;
            deleted = source.deleted;
            completed = source.completed;
            //child_id = source.child_id;
            TaskDate = source.TaskDate;
            if (includeOrdinal) Ordinal = source.Ordinal;
        }

        /// <summary>
        /// returns true if id,completed,name,notes and task date equals
        /// </summary>
        public bool DataEquals(Task other)
        {
            bool equals = true;
            equals &= other.id == id;
            equals &= other.completed == completed;
            //equals &= ArrayEquals(child_id, other.child_id);
            equals &= StringUtil.EqualsOrNull(other.name, name);
            equals &= StringUtil.EqualsOrNull(other.notes, notes);
            equals &= StringUtil.EqualsOrNull(other.task_date, task_date);
            return equals;
        }

        #endregion

        public void AddChild(Task task)
        {
            Children.Add(task);
            SetChildIdFromList();
        }

        public void RemoveChild(Task task)
        {
            Children.Remove(task);
            SetChildIdFromList();
        }

        private void SetChildIdFromList()
        {
            child_id = new string[Children.Count];
            for (int i = 0; i < child_id.Length; i++)
            {
                child_id[i] = Children[i].id;
            }
        }

        internal static bool ParentTaskEquals(Task one, Task two)
        {
            if (one == null || two == null) throw new ArgumentException("Null input in parentTaskEquals");
            if (one.ParentTask == null && two.ParentTask == null) return true;
            if (one.ParentTask != null && two.ParentTask == null) return false;
            if (one.ParentTask == null && two.ParentTask != null) return false;
            return one.ParentTask.id == two.ParentTask.id;
        }

        public Task GetPriorTask()
        {
            return ParentList.GetPriorSibling(this);
        }

        /// <summary>
        /// Sets new ID and updates all parent objects refering to this
        /// </summary>
        /// <param name="newId"></param>
        public void SetNewId(string newId)
        {
            ParentList.ReplaceChildIds(id, newId);
            id = newId;
        }

        /// <summary>
        /// Creates a new GUID as ID and updates all parent objects refering to this
        /// </summary>
        public void SetNewId()
        {
            SetNewId(Guid.NewGuid().ToString());
        }

        public int GetChildCountDeep()
        {
            return GetChildCountDeepRec(0);
        }
        private int GetChildCountDeepRec(int count)
        {
            count += Children.Count;
            foreach (Task child in Children) count += child.GetChildCountDeepRec(count);
            return count;
        }

        
        /// <summary>
        /// Adds ordinal recursively by N and saves each item
        /// </summary>
        /// <param name="n">What to add ordinal with</param>
        public void AddOrdinalDeep(int n)
        {
            Ordinal += n;
            //outlook.InsertOrUpdateTask(this, null);
            foreach (Task child in Children) child.AddOrdinalDeep(n);
        }

        /// <summary>
        /// Returns this task with all children recursively.
        /// </summary>
        /// <returns></returns>
        public List<Task> GetThisAndChildren()
        {
            var list = new List<Task>();
            GetThisAndChildren(list);
            return list;
        }

        private void GetThisAndChildren(List<Task> list)
        {
            list.Add(this);
            foreach (Task task in Children) task.GetThisAndChildren(list);
        }
    }
}
